package com.zcp;


import com.zcp.pcsuitall.MyDes;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 本类主要你用来测试向手机上传文件。
 * 
 * @author Administrator
 */
public class UpLoadFile {

    private static String sendFileDir ="E:\\1";
        //"E:\\私人\\照片\\2010.08.26福大篮球";

    // "E:\\私人\\音乐\\Music\\back";// 把这个文件夹下面的文件发送到手机中。

    private static String cipherKey="asdfghjk";
    public static void main(String[] args) {
        // 重定向端口
        try {
            Runtime.getRuntime()
                    .exec("E:\\work\\soft\\android-sdk-windows\\android-sdk-windows\\tools\\adb.exe forward tcp:8849 tcp:8849");
            Runtime.getRuntime()
                    .exec("E:\\work\\soft\\android-sdk-windows\\android-sdk-windows\\tools\\adb.exe forward tcp:8851 tcp:8851");

            Runtime.getRuntime()
                    .exec("E:\\work\\soft\\android-sdk-windows\\android-sdk-windows\\tools\\adb.exe forward tcp:8852 tcp:8852");
            Thread.sleep(3000);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            SocketChannel controlChannel = getSocketConnection(8849);
            SocketChannel fileChannel = getSocketConnection(8851);

//            String resetKey = "<?xml version=\"1.0\" encoding=\"utf-8\"?><root><action>ResetKey</action></root>";
//            // 发送数据
//            sendData(controlChannel, resetKey);
//            String result = writeDataToFile(controlChannel,10);
//            cipherKey = getRegularSingle("key", result,true);
            
            
            // 启用另一个传输通道。
            String startOtherFileTran = "<?xml version=\"1.0\" encoding=\"utf-8\"?><root><action>StartDataPort</action><data><key>8852</key></data></root>";
            sendData(controlChannel, startOtherFileTran);
            String message = writeDataToFile(controlChannel, 20);
            SocketChannel fileChannel2 = getSocketConnection(8852);

            // 发送这个文件夹下面的数据到手机中
            File file = new File(sendFileDir);

            boolean transportFrom1 = true;
            if (file.exists() && file.isDirectory()) {
                for (File element : file.listFiles()) {
                    // 只是发送文件
                    if (element.isFile()) {
                        //混合通道传输
                        if (transportFrom1) {
                            saveFile(element, 8851, fileChannel, controlChannel);
                            transportFrom1 = false;
                        } else {
                            saveFile(element, 8852, fileChannel2, controlChannel);
                            transportFrom1 = true;
                        }
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 向服务器发送保存文件。
     * 
     * @param element 要保存的文件。
     * @param port
     * @param channel
     * @param controlChannel
     */
    private static void saveFile(File element, int port, SocketChannel channel,
            SocketChannel controlChannel) {
        String message = "";
        String action = "";
        String opResult = "";
        String returnMessage = "";
        String key = "";

        System.out.println("send file " + element.getName());

        message = getSendMessage(element, String.valueOf(port));
        // 向服务器发送要 保存文件请求
        sendData(controlChannel, message);
        // 等待服务器返回消息
        returnMessage = writeDataToFile(controlChannel, 20);
        System.out.println("save File return "+returnMessage);
        action = getRegularSingle("action", returnMessage, true);
        opResult = getRegularSingle("result", returnMessage, true);
        if ("SaveFile".equals(action) && "true".equalsIgnoreCase(opResult)) {
            // 先服务器发送文件数据
            sendFile(channel, element.getPath(),element.length());
            // 等待服务保存文件结束消息
            returnMessage = writeDataToFile(controlChannel, 30);
            action = getRegularSingle("action", returnMessage, true);
            key = getRegularSingle("key", returnMessage, true);
            System.out.println("savefile message "+returnMessage);
            if ("SaveFileOk".equals(action)) {
                System.out.println("saveFile[" + element.getName() + "] by " + key + " ok");
            } else {
                System.out.println(element.getName() + " save fail");
            }

        } else {
            System.out.println("save File error");
        }
    }

    /**
     * 通过正则表达式获取xml指令中节点数据。
     * 
     * @param regularKey
     * @param source
     * @param replay
     * @return
     */
    private static String getRegularSingle(String regularKey, String source, boolean replay) {
        String result = null;
        if (replay) {
            source = source.replaceAll(">\\s*?\r*?\n*?\u0085*?\u2028*?\u2029*?\\s*?<", "><");// 去换行符处理
        }
        Pattern p = Pattern.compile("(?<=<" + regularKey
                + ">)(.*?[\r*?\n*?\u0085*?\u2028*?\u2029*?]*?.*?)(?=</" + regularKey + ">)");
        Matcher m = p.matcher(source);
        if (m.find()) {
            result = m.group(0);
        }
        return result;
    }

    /**
     * 获取发送消息,主要通知服务器要发送文件的：文件名，大小，通道。
     * 
     * @return
     */
    private static String getSendMessage(File file, String key) {
        StringBuffer sb = new StringBuffer();
        sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?><root><action>SaveFile</action><data>");
        sb.append("<fileName>" + "/sdcard/scj/kk/"+file.getName() + "</fileName>");
        sb.append("<key>" + key + "</key>");
        sb.append("<fileLength>" + file.length() + "</fileLength>");

        sb.append("</data></root>");

        return sb.toString();
    }

    /**
     * 获取连接端口。
     * 
     * @param port
     * @return
     * @throws IOException
     */
    private static SocketChannel getSocketConnection(int port) throws IOException {
        InetSocketAddress addr = new InetSocketAddress("127.0.0.1", port);//10.204.79.13
        // 生成一个socketchannel
        SocketChannel sc = SocketChannel.open();
        // 连接到server
        sc.connect(addr);
        while (!sc.finishConnect())
            ;
        return sc;
    }

    /**
     * 向通道中发送文件数据。
     * 
     * @param ssc
     * @param filePath
     */
    private static void sendFile(SocketChannel ssc, String filePath,long length) {
        FileInputStream fileInputStream = null;
        FileChannel inChannel = null;
        try {
            fileInputStream = new FileInputStream(filePath);
            inChannel = fileInputStream.getChannel();
            ByteBuffer w_buff = ByteBuffer.allocate(1024);
            long count=0;
            int kk=0;
            while (true) {
                int eof = inChannel.read(w_buff);
                if (eof == -1)
                    break;
                count=count+eof;
                w_buff.flip();
                EchoToService(ssc, w_buff);
                w_buff.clear();
                kk++;
                if (kk == 10000) {
                    System.out.println("send file " + filePath + "have " + ((count * 100) / length)
                            + "%");
                    kk = 0;
                }
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inChannel != null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 发送数据到通道服务器中。<br>
     * 
     * @param sc
     * @param write_buff
     * @throws IOException
     */
    private static void EchoToService(SocketChannel sc, ByteBuffer write_buff) throws IOException {
        while (write_buff.hasRemaining()) {
            sc.write(write_buff);
        }
    }

    /**
     * 发送指令数据。
     * 
     * @param sc
     * @param value
     */
    public static boolean sendData(SocketChannel sc, String value) {
        try {
            value=MyDes.encryptDES(value, cipherKey);
            ByteBuffer w_buff = ByteBuffer.allocate(value.length() * 2);
            w_buff.clear();
            w_buff.put(value.getBytes());
            w_buff.flip();
            EchoToService(sc, w_buff);
            w_buff.clear();
            return true;
        } catch (IOException ioe) {
            ioe.printStackTrace();
            return false;
        }
    }

    /**
     * 读取通道中数据，设置超时时间为second秒。
     * 
     * @param sc
     * @param second超时时间。
     * @return
     */
    public static String writeDataToFile(final SocketChannel sc, int second) {

        String result = null;
        ExecutorService executor = Executors.newSingleThreadExecutor();
        FutureTask<String> future = new FutureTask<String>(new Callable<String>() {
            public String call() {
                // 真正的任务在这里执行，这里的返回值类型为String，可以为任意类型
                return getDataFromChannel(sc);
            }
        });
        executor.execute(future);
        // 在这里可以做别的任何事情
        try {
            result = future.get(second, TimeUnit.SECONDS); // 取得结果，同时设置超时执行时间为6秒。同样可以用future.get()，不设置执行超时时间取得结果
        } catch (InterruptedException e) {
            e.printStackTrace();
            future.cancel(true);
        } catch (ExecutionException e) {
            e.printStackTrace();
            future.cancel(true);
        } catch (TimeoutException e) {
            e.printStackTrace();
            future.cancel(true);
        } finally {
            executor.shutdown();
        }
        return result;

    }

    private static final String ENDTAG = "</root>";

    /**
     * 把通道中数据读取到文件流中(如果文件太长，会分几次发送，所以这边要增加处理)。 1：修改解决分包读取问题。
     * 2：解决结束标识符号</root>被分开读取问题。 2：由于缓存中文被分开读取问题。
     * 
     * @param sc
     * @param toFile
     */
    public static String getDataFromChannel(SocketChannel sc) {
        ByteBuffer r_buff = ByteBuffer.allocate(1024);
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        try {
            r_buff.clear();
            int size = 0;
            boolean close = false;
            while (!close) {
                while ((size = sc.read(r_buff)) > 0) {
                    // 确保r_buff可读
                    r_buff.flip();
                    byteOut.write(r_buff.array(), 0, size);
                    // </root> 有可能会被分开读取（由于缓存大小限制是分开读取），所以需要增加判断。
                    byteOut.flush();
                    if ((MyDes.decryptDES(byteOut.toString("utf-8"),cipherKey).indexOf(ENDTAG) != -1)) {
                        close = true;
                        r_buff.clear();
                        break;
                    }
                    r_buff.clear();
                }
            }
            close = false;

        } catch (Exception e) {
            e.printStackTrace();
        }
        String result = null;
        try {
            byteOut.flush();
            result = MyDes.decryptDES(byteOut.toString("utf-8"),cipherKey);
            byteOut.close();
            byteOut = null;
            r_buff = null;
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return result;

    }
}
